# ManyToManyResolver.java

    package org.codefilarete.stalactite.engine.configurer.resolver.manytomany;

    import java.util.Collection;
    import java.util.function.Consumer;

    import org.codefilarete.stalactite.engine.configurer.AssociationRecordMapping;
    import org.codefilarete.stalactite.engine.configurer.IndexedAssociationRecordMapping;
    import org.codefilarete.stalactite.engine.configurer.model.IntermediaryRelationJoin;
    import org.codefilarete.stalactite.engine.configurer.model.ResolvedManyToManyRelation;
    import org.codefilarete.stalactite.engine.configurer.resolver.SkeletonAggregateResolver;
    import org.codefilarete.stalactite.engine.runtime.AssociationRecord;
    import org.codefilarete.stalactite.engine.runtime.AssociationRecordPersister;
    import org.codefilarete.stalactite.engine.runtime.AssociationTable;
    import org.codefilarete.stalactite.engine.runtime.ConfiguredRelationalPersister;
    import org.codefilarete.stalactite.engine.runtime.IndexedAssociationRecord;
    import org.codefilarete.stalactite.engine.runtime.IndexedAssociationTable;
    import org.codefilarete.stalactite.engine.runtime.onetomany.AbstractOneToManyEngine;
    import org.codefilarete.stalactite.engine.runtime.onetomany.IndexedAssociationTableManyRelationDescriptor;
    import org.codefilarete.stalactite.engine.runtime.onetomany.ManyRelationDescriptor;
    import org.codefilarete.stalactite.engine.runtime.onetomany.OneToManyWithAssociationTableEngine;
    import org.codefilarete.stalactite.engine.runtime.onetomany.OneToManyWithIndexedAssociationTableEngine;
    import org.codefilarete.stalactite.sql.ConnectionConfiguration;
    import org.codefilarete.stalactite.sql.Dialect;
    import org.codefilarete.stalactite.sql.ddl.structure.Column;
    import org.codefilarete.stalactite.sql.ddl.structure.Table;

    import static org.codefilarete.stalactite.dsl.property.CascadeOptions.RelationMode.ALL_ORPHAN_REMOVAL;
    import static org.codefilarete.stalactite.dsl.property.CascadeOptions.RelationMode.ASSOCIATION_ONLY;
    import static org.codefilarete.stalactite.dsl.property.CascadeOptions.RelationMode.READ_ONLY;

    /**
     * Handles write-cascade wiring for a resolved {@link ResolvedManyToManyRelation}.
     * Many-to-many relations always use an intermediary association table; there is no "owned by reverse side" variant.
     * Two sub-paths are supported:
     * <ul>
     *   <li>Non-indexed: {@link AssociationTable} + {@link OneToManyWithAssociationTableEngine}</li>
     *   <li>Indexed: {@link IndexedAssociationTable} + {@link OneToManyWithIndexedAssociationTableEngine}</li>
     * </ul>
     * The SELECT join-tree wiring is handled separately by {@link AggregateManyToManyAppender}.
     *
     * @author Guillaume Mary
     */
    public class ManyToManyResolver {

    	private final SkeletonAggregateResolver skeletonAggregateResolver;
    	private final Dialect dialect;
    	private final ConnectionConfiguration connectionConfiguration;

    	public ManyToManyResolver(SkeletonAggregateResolver skeletonAggregateResolver,
    	                          Dialect dialect,
    	                          ConnectionConfiguration connectionConfiguration) {
    		this.skeletonAggregateResolver = skeletonAggregateResolver;
    		this.dialect = dialect;
    		this.connectionConfiguration = connectionConfiguration;
    	}

    	/**
    	 * Resolves the given many-to-many relation by building a persister for the target entity and wiring write cascades
    	 * (INSERT / UPDATE / DELETE) onto the source persister via the appropriate association-table engine.
    	 *
    	 * @param resolvedRelation      the resolved model relation carrying the join structure and cascade options
    	 * @param sourcePersister       the persister that owns the collection
    	 * @param createdPersisterConsumer a consumer that receives the freshly built target persister
    	 */
    	public <SRC, SRCID, TRGT, TRGTID, S extends Collection<TRGT>,
    			LEFTTABLE extends Table<LEFTTABLE>,
    			RIGHTTABLE extends Table<RIGHTTABLE>>
    	void resolve(ResolvedManyToManyRelation<SRC, TRGT, S, SRCID, TRGTID, LEFTTABLE, RIGHTTABLE> resolvedRelation,
    	             ConfiguredRelationalPersister<SRC, SRCID> sourcePersister,
    	             Consumer<ConfiguredRelationalPersister<TRGT, TRGTID>> createdPersisterConsumer) {

    		ConfiguredRelationalPersister<TRGT, TRGTID> targetPersister =
    				skeletonAggregateResolver.buildPersister(resolvedRelation.getTargetEntity());
    		createdPersisterConsumer.accept(targetPersister);

    		AbstractOneToManyEngine<SRC, TRGT, SRCID, TRGTID, S> engine;
    		if (resolvedRelation.isOrdered()) {
    			engine = buildIndexedAssociationTableEngine(sourcePersister, resolvedRelation, targetPersister);
    		} else {
    			engine = buildAssociationTableEngine(sourcePersister, resolvedRelation, targetPersister);
    		}

    		boolean writeAuthorized = resolvedRelation.getRelationMode() != READ_ONLY;
    		if (writeAuthorized) {
    			engine.addInsertCascade(targetPersister);
    			engine.addUpdateCascade(targetPersister);
    			engine.addDeleteCascade(targetPersister);
    		}
    	}

    	/**
    	 * Builds the engine for the non-indexed case.
    	 * The {@link ManyRelationDescriptor} is built with the pre-assembled {@link org.codefilarete.stalactite.sql.result.BeanRelationFixer}
    	 * from the model relation, which already handles optional bidirectionality.
    	 */
    	@SuppressWarnings("unchecked")
    	private <SRC, SRCID, TRGT, TRGTID, S extends Collection<TRGT>,
    			LEFTTABLE extends Table<LEFTTABLE>,
    			RIGHTTABLE extends Table<RIGHTTABLE>,
    			ASSOCIATIONTABLE extends AssociationTable<ASSOCIATIONTABLE, LEFTTABLE, RIGHTTABLE, SRCID, TRGTID>>
    	AbstractOneToManyEngine<SRC, TRGT, SRCID, TRGTID, S>
    	buildAssociationTableEngine(ConfiguredRelationalPersister<SRC, SRCID> sourcePersister,
    	                            ResolvedManyToManyRelation<SRC, TRGT, S, SRCID, TRGTID, LEFTTABLE, RIGHTTABLE> resolvedRelation,
    	                            ConfiguredRelationalPersister<TRGT, TRGTID> targetPersister) {

    		IntermediaryRelationJoin<LEFTTABLE, RIGHTTABLE, ASSOCIATIONTABLE, SRCID, TRGTID> join =
    				(IntermediaryRelationJoin<LEFTTABLE, RIGHTTABLE, ASSOCIATIONTABLE, SRCID, TRGTID>) resolvedRelation.getJoin();

    		ManyRelationDescriptor<SRC, TRGT, S> manyRelationDescriptor = new ManyRelationDescriptor<>(
    				resolvedRelation.getAccessor(),
    				resolvedRelation.getComponentFactory(),
    				null,   // bidirectionality is pre-baked in the relation fixer below
    				resolvedRelation.getRelationFixer(),
    				resolvedRelation.getRelationMode() == ASSOCIATION_ONLY,
    				resolvedRelation.getRelationMode() == ALL_ORPHAN_REMOVAL);

    		AssociationRecordPersister<AssociationRecord, ASSOCIATIONTABLE> associationPersister = new AssociationRecordPersister<>(
    				new AssociationRecordMapping<>(
    						join.getJoinTable(),
    						sourcePersister.getMapping().getIdMapping().getIdentifierAssembler(),
    						targetPersister.getMapping().getIdMapping().getIdentifierAssembler()),
    				dialect,
    				connectionConfiguration);

    		return new OneToManyWithAssociationTableEngine<>(
    				sourcePersister,
    				targetPersister,
    				manyRelationDescriptor,
    				associationPersister,
    				dialect.getWriteOperationFactory(),
    				dialect);
    	}

    	/**
    	 * Builds the engine for the indexed case (association table carries a positional index column).
    	 */
    	@SuppressWarnings("unchecked")
    	private <SRC, SRCID, TRGT, TRGTID, S extends Collection<TRGT>,
    			LEFTTABLE extends Table<LEFTTABLE>,
    			RIGHTTABLE extends Table<RIGHTTABLE>,
    			ASSOCIATIONTABLE extends IndexedAssociationTable<ASSOCIATIONTABLE, LEFTTABLE, RIGHTTABLE, SRCID, TRGTID>>
    	AbstractOneToManyEngine<SRC, TRGT, SRCID, TRGTID, S>
    	buildIndexedAssociationTableEngine(ConfiguredRelationalPersister<SRC, SRCID> sourcePersister,
    	                                   ResolvedManyToManyRelation<SRC, TRGT, S, SRCID, TRGTID, LEFTTABLE, RIGHTTABLE> resolvedRelation,
    	                                   ConfiguredRelationalPersister<TRGT, TRGTID> targetPersister) {

    		IntermediaryRelationJoin<LEFTTABLE, RIGHTTABLE, ASSOCIATIONTABLE, SRCID, TRGTID> join =
    				(IntermediaryRelationJoin<LEFTTABLE, RIGHTTABLE, ASSOCIATIONTABLE, SRCID, TRGTID>) resolvedRelation.getJoin();

    		IndexedAssociationTableManyRelationDescriptor<SRC, TRGT, S, SRCID> manyRelationDescriptor =
    				new IndexedAssociationTableManyRelationDescriptor<>(
    						resolvedRelation.getAccessor(),
    						resolvedRelation.getComponentFactory(),
    						null,   // bidirectionality is pre-baked in the relation fixer
    						sourcePersister.getMapping()::getId,
    						resolvedRelation.getRelationMode() == ASSOCIATION_ONLY,
    						resolvedRelation.getRelationMode() == ALL_ORPHAN_REMOVAL);

    		Column<ASSOCIATIONTABLE, Integer> indexColumn = resolvedRelation.getIndexingAssociationColumn();

    		AssociationRecordPersister<IndexedAssociationRecord, ASSOCIATIONTABLE> indexedAssociationPersister =
    				new AssociationRecordPersister<>(
    						new IndexedAssociationRecordMapping<>(
    								join.getJoinTable(),
    								sourcePersister.getMapping().getIdMapping().getIdentifierAssembler(),
    								targetPersister.getMapping().getIdMapping().getIdentifierAssembler(),
    								join.getJoinTable().getLeftIdentifierColumnMapping(),
    								join.getJoinTable().getRightIdentifierColumnMapping()),
    						dialect,
    						connectionConfiguration);

    		return new OneToManyWithIndexedAssociationTableEngine<>(
    				sourcePersister,
    				targetPersister,
    				manyRelationDescriptor,
    				indexedAssociationPersister,
    				indexColumn,
    				dialect.getWriteOperationFactory(),
    				dialect);
    	}
    }